﻿#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Badger.Vhdl.Types;
using Weazel.Badger.Vhdl.Statements;

#endregion

namespace Weazel.Badger
{
  public abstract class ConversionEntity : RegisterEntity
  {
		private ConversionContext conversionContext;
    protected Gezel.Model.Controller controller;

    protected Vhdl.Process outPortUpdateProcess;
    private Predefined.DisplayTraceProcess displayProcess;



    private int lookupTableUniqueNameNumber = 0;

    private Dictionary<string, Vhdl.ArrayTypeValue> lookUpTables =
      new Dictionary<string, Vhdl.ArrayTypeValue>();

		private Dictionary<string, Vhdl.ConstantDeclaration> convertedLookUpTables =
			new Dictionary<string, Vhdl.ConstantDeclaration>();

    private Dictionary<string, ExpressionConverter> convertedSignalFlowGraphExpressions = 
      new Dictionary<string,ExpressionConverter>();

    private Dictionary<string, Gezel.Model.LeafExpression> constants =
      new Dictionary<string, Weazel.Gezel.Model.LeafExpression>();

    #region Properties
		protected ConversionContext ConversionContext
		{
			get { return conversionContext; }
		}

    public Gezel.Model.Controller Controller
    {
      get
      {
        return controller;
      }

      set
      {
        controller = value;
      }
    }

		protected Predefined.DisplayTraceProcess DisplayProcess
		{
			get
			{
				return displayProcess;
			}

			set
			{
				displayProcess = value;
			}
		}

    public Dictionary<string, Vhdl.ArrayTypeValue> LookUpTables
    {
      get
      {
        return lookUpTables;
      }

      set
      {
        lookUpTables = value;
      }
    }

		public Dictionary<string, Vhdl.ConstantDeclaration> ConvertedLookUpTables
		{
			get { return convertedLookUpTables; }
			set { convertedLookUpTables = value; }
		}

    public Dictionary<string, ExpressionConverter> ConvertedSignalFlowGraphExpressions
    {
      get
      {
        return convertedSignalFlowGraphExpressions;
      }

      set
      {
        convertedSignalFlowGraphExpressions = value;
      }
    }

    public Dictionary<string, Gezel.Model.LeafExpression> Constants
    {
      get
      {
        return constants;
      }

      set
      {
        constants = value;
      }
    }

    #endregion

    #region Constructor
    public ConversionEntity(ConversionContext conversionContext, Gezel.Model.Controller controller) 
			: base(controller.Datapath.LocalName)
    {
			// copy references ..
			this.conversionContext = conversionContext;
      this.controller = controller;
			
			// Save a reference to the list constants defined in the Gezel model
			this.constants = this.controller.Model.Constants;

			// Create the clock port for this entity
			this.ClockPort = 
				new Weazel.Badger.Vhdl.Port(
					new StdLogic(),
					null,
					conversionContext.Configuration.ClockName,
					Weazel.Badger.Vhdl.PortDirection.In,
					-1
				);

			// Create the reset port for this entity
			this.ResetPort =
				new Weazel.Badger.Vhdl.Port(
					new StdLogic(),
					null,
					conversionContext.Configuration.ResetName,
					Weazel.Badger.Vhdl.PortDirection.In,
					-1
				);

      this.outPortUpdateProcess = 
        new Weazel.Badger.Vhdl.Process("out_port_update", this);

      // this process is later removed if it is not needed
			if (conversionContext.Configuration.ConvertDisplayDirectives)
			{
				this.displayProcess =
				 new Predefined.DisplayTraceProcess("display", controller.Datapath.LocalName + "_display.txt", this, ResetPort, ClockPort);
				AddProcess(displayProcess);
			}
      
      AddProcess(outPortUpdateProcess);

      foreach (Gezel.Model.Signal signal in controller.Datapath.Signals.Values)
        addSignal(signal);

      foreach (Gezel.Model.Register register in controller.Datapath.Registers.Values)
        addRegister(register);

      foreach (Gezel.Model.InPort port in controller.Datapath.InPorts.Values)
        addInPort(port, port.DeclarationPosition);

      foreach (Gezel.Model.OutPort port in controller.Datapath.OutPorts.Values)
        addOutPort(port, port.DeclarationPosition);

      foreach (Gezel.Model.LookupTable table in controller.Datapath.LookupTables.Values)
        addLookupTable(table);

      convertSignalFlowGraphExpressions();

      // add clock and reset port
      AddPort(ClockPort);
      AddPort(ResetPort);
    }
    #endregion

		public void SetupChildDatapaths()
		{
			foreach (string childDatapathName in controller.Datapath.ChildDatapathRefs.Keys)
			{
				// child datapaths can either be instantiated directly from their implementation 
				// or from using a "copy" use.
				Vhdl.ComponentDeclaration component = null;

				// first check of the name of the child is in the list of datapaths
				if (conversionContext.Datapaths.ContainsKey(childDatapathName))
				{
					// get and add a component instantiation of this datapath
					component = conversionContext.Datapaths[childDatapathName].GetDeclaration();

				}
				// second, check if the name is in the list of datapath clones
				else if (conversionContext.ClonedDatapathToDatapathMap.ContainsKey(childDatapathName))
				{
					// first get a refence to the implementation of the child datapath
					string childDatapathImplementationName =
						conversionContext.ClonedDatapathToDatapathMap[childDatapathName];

					// get and add a component instantiation of this datapath
					component =
						conversionContext.Datapaths[childDatapathImplementationName].GetDeclaration();
				}
				else if (conversionContext.IpBlocks.ContainsKey(childDatapathName))
				{
					component =
						conversionContext.IpBlocks[childDatapathName];
				}
				// third, we give up ..
				else
				{
					string message =
						string.Format("Failed to locate implementation of child datapath: '{0}'.", childDatapathName);

					throw new Vhdl.VhdlModelException(message);
				}

				// add to map of children
				Children.Add(childDatapathName, component);
				
				// add the declaration to this entity

				// only add this declaration if it does not already exist
				bool addComponent = true;

				foreach (Vhdl.IDeclarativeItem item in DeclarativeItems)
					if (item is Vhdl.ComponentDeclaration && ((Vhdl.ComponentDeclaration)item).CompareTo(component))
						addComponent = false;

				if (addComponent)
					AddDeclarativeItem(component);

				// create a port map
				Vhdl.PortMap portMap = new Weazel.Badger.Vhdl.PortMap(
					"label_" + childDatapathName,
					component.Name
				);

				string[] signalNames = controller.Datapath.ChildDatapathRefs[childDatapathName];

				// map ports to signals for all position minus clock and reset..

				for (int i = 0; i < signalNames.Length; i++)
				{
					portMap.Add(component.PortNameAtPosition(i), signalNames[i]);
				}

				portMap.Add(conversionContext.Configuration.ClockName, conversionContext.Configuration.ClockName);
				portMap.Add(conversionContext.Configuration.ResetName, conversionContext.Configuration.ResetName);

				AddPortMap(portMap);
			}
		}

		/// <summary>
		/// Convinience method to get the VHDL type of a gezel typed entity.
		/// </summary>
		/// <param name="e">Gezel typed entity</param>
		/// <returns>VHDL type of entity</returns>
		protected Vhdl.Types.Type GetVhdlType(Gezel.Model.ITypedEntity e)
		{
			return e.Width == 1 ?
				(Vhdl.Types.Type)new StdLogic() :
				(Vhdl.Types.Type)new StdLogicVector(e.Width);
		}

		/// <summary>
		/// Converts the signal flow graphs of this datapath.
		/// </summary>
    private void convertSignalFlowGraphExpressions()
    {
      foreach (Gezel.Model.SignalFlowGraph sfg in controller.SignalFlowGraphs.Values)
      {
        ExpressionConverter converter = new ExpressionConverter(this, sfg);

        // list of display expressions, converted after conversion of sfg computations
        List<Gezel.Model.Expression> displayExpressions =
          new List<Weazel.Gezel.Model.Expression>();

        if (sfg.Signal != null)
        {
          Vhdl.Types.Type type = GetVhdlType(sfg.Signal);

          Vhdl.Signal vhdlSignal = 
            new Weazel.Badger.Vhdl.Signal(
              type,
              new Vhdl.GezelType(sfg.Signal.Signed, sfg.Signal.Width),
              sfg.LocalName + "_" + sfg.Signal.LocalName
            );

          AddSignal(vhdlSignal);
          AddDeclarativeItem(vhdlSignal);
        }

        if (sfg.Register != null)
        {
          addRegister(sfg.Register);
        }

				foreach (Gezel.Model.Expression expression in sfg.Expressions)
				{
					if (expression.ExpressionType == Weazel.Gezel.Model.ExpressionType.Assignment)
					{
						converter.ConvertAssignmentExpression((Gezel.Model.BinaryExpression)expression, true);
					}
					else if (expression.ExpressionType == Weazel.Gezel.Model.ExpressionType.DisplayDirective)
					{
						displayExpressions.Add(expression);
					}
				}
					
        if (displayExpressions.Count > 0 && conversionContext.Configuration.ConvertDisplayDirectives)
        {
          foreach (Gezel.Model.Expression display in displayExpressions)          
            converter.ConvertExpression(converter.Statements, display, true);          
        }

        convertedSignalFlowGraphExpressions.Add(sfg.LocalName, converter);
      }
    }

    private void addSignal(Gezel.Model.Signal signal)
    {
      Vhdl.Types.Type type = GetVhdlType(signal);
      Vhdl.Signal vhdlSignal = 
				new Weazel.Badger.Vhdl.Signal(
					type, 
					new Vhdl.GezelType(signal.Signed, signal.Width), 
					signal.LocalName
				);

      AddSignal(vhdlSignal);
      AddDeclarativeItem(vhdlSignal);      
    }

		private void addRegister(Gezel.Model.Register register)
		{
			Vhdl.Types.Type type = GetVhdlType(register);
			string name = register.LocalName;

			VhdlRegister r = new VhdlRegister(this, type, new Vhdl.GezelType(register.Signed, register.Width), name);
			
			Registers.Add(register.LocalName, r);
		}

    private void addInPort(Gezel.Model.InPort port, int position)
    {
      Vhdl.Types.Type type = GetVhdlType(port);
      Vhdl.Port vhdlPort = 
				new Vhdl.Port(
					type, 
					new Vhdl.GezelType(port.Signed, port.Width), 
					port.LocalName, 
					Vhdl.PortDirection.In,
					position
				);
      AddPort(vhdlPort);
    }

    private void addOutPort(Gezel.Model.OutPort port, int position)
    {
      Vhdl.Types.Type type = GetVhdlType(port);
      
      Vhdl.Port vhdlPort = 
        new Vhdl.Port(
          type,
          new Vhdl.GezelType(port.Signed, port.Width),
          "out_" + port.LocalName, 
          Vhdl.PortDirection.Out,
					position
        );

      Vhdl.Signal signal =
        new Weazel.Badger.Vhdl.Signal(
          type,
          new Vhdl.GezelType(port.Signed, port.Width),
          port.LocalName
        );

      outPortUpdateProcess.AddSignalToSensivitityList(signal);

      SignalAssignmentStatement portUpdateStmt =
        new SignalAssignmentStatement(
					vhdlPort, 
					new Vhdl.Expressions.SignalReferenceExpression(outPortUpdateProcess.Statements, signal)
				);

      outPortUpdateProcess.Statements.Add(portUpdateStmt);

      AddDeclarativeItem(signal);
      AddSignal(signal);
      AddPort(vhdlPort);
    }
   
    private void addLookupTable(Gezel.Model.LookupTable table)
    { 
			// vhdl seems unable to handle arrays of length 1 .. so ..
			if (table.Values.Length == 1)
			{
				// implement the table as a constant signal
				Vhdl.ConstantDeclaration constant = 
					new Weazel.Badger.Vhdl.ConstantDeclaration(
						new Integer(table.Width), 
						new Weazel.Badger.Vhdl.GezelType(table.Signed, table.Width),
						table.LocalName,
						table.Values[0]
					);

				// add at entity level
				AddDeclarativeItem(constant);

				convertedLookUpTables.Add(table.LocalName, constant);
			}
			else
			{
				UserDefinedArrayType tableType =
					new UserDefinedArrayType(
						"rom_table_" + lookupTableUniqueNameNumber.ToString(),
					 table.Values.Length,
					 new StdLogicVector(table.Width)
					);

				AddDeclarativeItem(tableType);

				Vhdl.ArrayTypeValue tab = new Vhdl.ArrayTypeValue(table.LocalName, true, tableType);

				foreach (Weazel.Math.Integer i in table.Values)
					tab.AddEntry(new Vhdl.Value(new StdLogicVector(table.Width), i.ToString()));

				lookUpTables.Add(table.LocalName, tab);
				lookupTableUniqueNameNumber++;

				AddDeclarativeItem(tab);
			}
    }
 
    public abstract void Convert();
  }
}
